package com.lagou.java.mvcframework.servlet;

import com.lagou.java.mvcframework.annotations.*;
import com.lagou.java.mvcframework.pojo.Handler;
import org.apache.commons.lang3.StringUtils;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class LagouDispatcherServlet extends HttpServlet {

    private Properties properties = new Properties();
    //ioc容器
    private Map<String, Object> ioc = new HashMap<>();

    //handlerMapping
    //private Map<String, Method>  handlerMapping = new HashMap<>();
    private List<Handler> handlerMapping = new ArrayList<>();


    @Override
    public void init(ServletConfig config) throws ServletException {
        //加载配置文件springmvc.properties
        String contextConfigLocation = config.getInitParameter("contextConfigLocation");
        doLoadConfig(contextConfigLocation);

        //扫描相关的类, 扫描注解
        doScan(properties.getProperty("scanPackage"));

        //初始化bean对象, 实现(ioc容器, 基于注解)
        doInstance();

        //实现依赖注入
        doAutowired();

        //构造一个handlerMapping处理器映射器, 将配置好的uri和method建立映射关系
        initHandlerMapping();

        System.out.println("spring mvc 初始化完成...");
        //等待请求进入, 处理请求
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //处理请求: 根据url找到对应的method方法, 进行调用, 并进行处理
        String requestUrl = req.getRequestURI();
//        Method method = handlerMapping.get(requestUrl);//获取到一个反射方法
        //反射调用, 需要传入对象, 需要传入参数, 此处无法完成调用, 没有把对象缓存起来, 也没有参数!!!!
//        method.invoke();

        Handler handler = getHandler(req);

        if(handler == null) {
            resp.getWriter().write("error, 404 not found!");
            return ;
        }

        Class<?>[] parameterTypes = handler.getMethod().getParameterTypes();

        Object[] params = new Object[parameterTypes.length];

        Map<String, String[]> parameterMap = req.getParameterMap();

        List<String> queryNames = new ArrayList<>();
        for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
            if(entry.getKey().equals("name")) {
                queryNames.addAll(Arrays.asList(entry.getValue()));
                if(!handler.getUserNames().containsAll(queryNames))  {
//                    resp.setCharacterEncoding(StandardCharsets.UTF_8.name());
                    resp.setContentType("text/html;charset=UTF-8");
                    resp.getWriter().write("用户名:" + Arrays.toString(entry.getValue()) + "没有访问权限");
                    return;
                }
            }
            //name=1&name=2, name [1,2]
            String value = StringUtils.join(entry.getValue(), ",");
            //如果没有值, 则跳过
            if(!handler.getParameterIndexMapping().containsKey(entry.getKey())) continue;
            //如果有值, 放入参数
            Integer index = handler.getParameterIndexMapping().get(entry.getKey());
            params[index] = value;//把前台传过来的参数值填充到对应的位置
        }

        int requetIndex = handler.getParameterIndexMapping().get(HttpServletRequest.class.getSimpleName());
        params[requetIndex] = req;

        int responseIndex = handler.getParameterIndexMapping().get(HttpServletResponse.class.getSimpleName());
        params[responseIndex] = resp;

        Method method = handler.getMethod();
        try {
            method.invoke(handler.getController(), params);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

    }

    private Handler getHandler(HttpServletRequest req) {
        if(handlerMapping.isEmpty()) return null;
        String url = req.getRequestURI();
        for (Handler handler : handlerMapping) {
            Matcher matcher = handler.getPattern().matcher(url);
            if(!matcher.matches()) continue;
            return handler;
        }
        return null;
    }

    //初始化一个HandlerMapping处理器映射器

    /**
     * 关键环节
     *  目的:
     *      将url和method建立关联
     */
    private void initHandlerMapping() {
        if(ioc.isEmpty()) return;

        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            Class<?> aClass = entry.getValue().getClass();
            if(!aClass.isAnnotationPresent(LagouRequestMapping.class)) continue;

            String baseUrl = "";
            List<String> allowedALLNames = new ArrayList<>();
            if(aClass.isAnnotationPresent(LagouRequestMapping.class)) {
                LagouRequestMapping classAnnotation = aClass.getAnnotation(LagouRequestMapping.class);
                baseUrl = classAnnotation.value();
            }
            if(aClass.isAnnotationPresent(Security.class)) {
                Security security = aClass.getAnnotation(Security.class);
                allowedALLNames.addAll(Arrays.asList(security.value()));
            }

            Method[] declaredMethods = aClass.getDeclaredMethods();
            for (Method method : declaredMethods) {
                if (!method.isAnnotationPresent(LagouRequestMapping.class)) {
                    continue;
                }
                List<String> allowedNames = new ArrayList<>();
                allowedNames.addAll(allowedALLNames);
                if(method.isAnnotationPresent(Security.class)) {
                    Security security = method.getAnnotation(Security.class);
                    allowedNames.addAll(Arrays.asList(security.value()));
                }


                LagouRequestMapping annotation = method.getAnnotation(LagouRequestMapping.class);
                String methodUrl = annotation.value();
                String url = baseUrl + methodUrl;

                //建立url和method之间的映射关系(map缓存起来)
//                handlerMapping.put(url, method);

                //把method 的所有信息封装成一个handler
                Handler handler = new Handler(entry.getValue(), method, Pattern.compile(url), allowedNames);
                //计算方法的参数位置信息 query(HttpServletRequest request, HttpServletResponse response, String name)
                Parameter[] parameters = method.getParameters();
                for (int i = 0; i < parameters.length; i++) {
                    Parameter parameter = parameters[i];
                    if(parameter.getType() == HttpServletResponse.class || parameter.getType() == HttpServletRequest.class) {
                        handler.getParameterIndexMapping().put(parameter.getType().getSimpleName(), i);
                    } else {
                        handler.getParameterIndexMapping().put(parameter.getName(), i);
                    }
                }
                handlerMapping.add(handler);
            }

        }

    }

    //实现依赖注入
    private void doAutowired() {
        if(ioc.isEmpty()) return;
        //有对象, 在进行依赖注入处理

        //遍历容器, 有LagouAurowired注解的字段维护依赖关系
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            Field[] fields = entry.getValue().getClass().getDeclaredFields();
            for (Field field : fields) {
                if(!field.isAnnotationPresent(LagouAurowired.class)) continue;

                //有该注解
                LagouAurowired annotation = field.getAnnotation(LagouAurowired.class);
                String beanName = annotation.value();//需要注入的beanId
                if("".equals(beanName.trim())) {
                    //如果没有配置具体的beanID, 那么就需要根据当前字段类型注入(接口注入)
                    beanName = field.getType().getName();
                }

                //开启赋值
                field.setAccessible(true);
                try {
                    field.set(entry.getValue(), ioc.get(beanName));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    //实现ioc容器
    private void doInstance() {
        if(classNames.size() == 0) return;

        try {
            for (int i = 0; i < classNames.size(); i++) {
                String className = classNames.get(i); //com.lagou.demo.controller.DemoController
                //反射
                Class<?> aClass = null;
                try {
                    aClass = Class.forName(className);
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
                if(aClass.isAnnotationPresent(LagouController.class)) {
                    //controller的id此处不做过多处理, 不取value了, 直接用首字母的小写作为id, 存入ioc容器
                    String simpleName = aClass.getSimpleName();
                    String lowFirstSimpleName = lowerFirst(simpleName);
                    Object o = null;
                    try {
                        o = aClass.getDeclaredConstructor().newInstance();
                    } catch (InstantiationException e) {
                        e.printStackTrace();
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    } catch (InvocationTargetException e) {
                        e.printStackTrace();
                    } catch (NoSuchMethodException e) {
                        e.printStackTrace();
                    }
                    ioc.put(lowFirstSimpleName, o);
                } else if (aClass.isAnnotationPresent(LagouService.class)) {
                    LagouService annotation = aClass.getAnnotation(LagouService.class);
                    //获取value值
                    String beanName = annotation.value();
                    if(!"".equals(beanName.trim())) {
                        ioc.put(beanName, aClass.newInstance());
                    } else {
                        beanName = lowerFirst(aClass.getSimpleName());
                        ioc.put(beanName, aClass.newInstance());
                    }

                    //service层往往是有接口的,面向接口开发, 此时再以接口名为id, 放入一份对象到ioc中, 便于后期根据接口类型注入
                    Class<?>[] interfaces = aClass.getInterfaces();
                    for (Class<?> anInterface : interfaces) {
                        //以接口的全限定类名为id放入
                        ioc.put(anInterface.getName(), aClass.newInstance());
                    }

                } else continue;
            }
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    private String lowerFirst(String simpleName) {
        char[] chars = simpleName.toCharArray();
        chars[0] = 'A' <= chars[0] && chars[0] <= 'Z' ? (char)(chars[0] + 32) : chars[0];
        return String.valueOf(chars);
    }


    private List<String> classNames = new ArrayList<>();
    //扫描类
    private void doScan(String scanPackage) {
        String scanPackagePath = Thread.currentThread().getContextClassLoader().getResource("").getPath() + scanPackage.replaceAll("\\.", "\\\\");
        File pack = new File(scanPackagePath);
        File[] files = pack.listFiles();
        for (File file : files) {
            if(file.isDirectory())
                doScan(scanPackage + "." + file.getName());
            else if (file.getName().endsWith(".class")) {
                String className = scanPackage + "." + file.getName().replaceAll("\\.class", "");
                classNames.add(className);
            }
        }
    }

    //加载配置文件
    private void doLoadConfig(String contextConfigLocation) {
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        try {
            properties.load(resourceAsStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }





}
